import defaultOptions from "./config";
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
class Methods {
    constructor(Vim, options = {}) {
        this.$Vim = Vim
        this.inOptions = Object.assign({}, options)
        this.options = Object.assign({}, defaultOptions, options)
        this.count = 0
        this.init()
    }
    init() {
        this.initPdf()
        this.getA4WH()
        this.createTempCanvas()
    }
    // 初始化 jspdf
    initPdf() {
        let pdfOption = {
            orientation: "p", // Orientation of the first page. Possible values are "portrait" or "landscape" (or shortcuts "p" or "l")
            unit: "pt", // Measurement unit (base unit) to be used when coordinates are specified. Possible values are "pt" (points), "mm", "cm", "in", "px", "pc", "em" or "ex". Note that in order to get the correct scaling for "px" units, you need to enable the hotfix "px_scaling" by setting options.hotfixes = ["px_scaling"]
            format: "a4", // The format of the first page
            floatPrecision: 16, // or "smart", default is 16
        };
        this.options.pdf = new jsPDF(pdfOption);
    }
    // 获取a4纸张大小转换的px
    getA4WH() {
        // 获取设备像素比率
        // let devicePixelRatio = window.devicePixelRatio || 1;
        let definition = this.options.qualityObject[this.options.quality]
        this.options.html2canvasConfig.scale = this.options.ratio = definition;
        
        let arrDPI = this.js_getDPI()
        this.options.xDpi = arrDPI[0];
        this.options.yDpi = arrDPI[1];
        this.options.a4W = this.options.a4[this.options.xDpi][0];
        this.options.a4H = this.options.a4[this.options.yDpi][1];
    }
    // 获取 deviceXYDPI(每英寸水平的点数)
    js_getDPI() {
        let arrDPI = [];
        if (window.screen.deviceXDPI != undefined) {
            arrDPI[0] = window.screen.deviceXDPI;
            arrDPI[1] = window.screen.deviceYDPI;
        } else {
            let tmpNode = document.createElement("div");
            tmpNode.style.cssText =
                "width:1in;height:1in;position:absolute;left:0px;top:0px;z-index:99;visibility:hidden";
            document.body.appendChild(tmpNode);
            arrDPI[0] = parseInt(tmpNode.offsetWidth);
            arrDPI[1] = parseInt(tmpNode.offsetHeight);
            tmpNode.parentNode.removeChild(tmpNode);
        }
        return arrDPI;
    }
    // 创建一个临时的canvas
    createTempCanvas() {
        this.options.copyCanvas = document.createElement("canvas");
        this.options.copyCanvas.width = this.options.a4W * this.options.ratio; // 同步元素对象生成的 canvas对象宽度和临时的canvas宽度一致，防止绘制的图像模糊和拉伸
        this.options.copyCanvas.height = this.options.a4H * this.options.ratio; // 同步元素对象生成的 canvas对象高度和临时的canvas高度一致，防止绘制的图像模糊和拉伸
        this.options.copyCanvas.style.width = this.options.a4W + 'px'; // 使用 css 设置临时的 canvas宽度和元素对象生成的 canvas对象css宽度一致，防止绘制的图像模糊和拉伸
        this.options.copyCanvas.style.height = this.options.a4H + 'px'; // 使用 css 设置临时的 canvas高度和元素对象生成的 canvas对象css高度一致，防止绘制的图像模糊和拉伸
        this.options.copyContext = this.options.copyCanvas.getContext("2d", { willReadFrequently: true });
        this.options.copyContext.fillStyle = "#ffffff"; // 填充背景默认为白色
        this.options.copyContext.fillRect(0, 0, this.options.copyCanvas.width, this.options.copyCanvas.height); // 填充至整个页面
    }
    // 生成文件
    async generateFile({ homeEl = null, headEl = null, footEl = null, pagesEl = [[]], previewBox = null }) {
        return new Promise(async (resolve, reject) => {
            try {
                if (this.options.showLoading) {
                    this.showLoading('请勿关闭浏览器，文件生成中...')
                }
                // 存储html元素
                this.options.homeEl = homeEl
                this.options.headEl = headEl
                this.options.footEl = footEl
                this.options.pagesEl = pagesEl
                // 预览盒子元素对象
                this.options.previewBox = previewBox
                // 存储转换的 canvas
                this.options.headCanvas = await html2canvas(headEl, this.options.html2canvasConfig);
                this.options.footCanvas = await html2canvas(footEl, this.options.html2canvasConfig);
                this.options.homeCanvas = await html2canvas(homeEl, this.options.html2canvasConfig);

                let pagesCanvas = [];
                for (let i = 0; i < pagesEl.length; i++) {
                    let part = []
                    let pageEl = pagesEl[i];
                    for (let j = 0; j < pageEl.length; j++) {
                        let elCvs = await html2canvas(pageEl[j], this.options.html2canvasConfig);
                        part.push(elCvs)
                    }
                    pagesCanvas.push(part)
                }
                this.options.pagesCanvas = pagesCanvas;
                this.dealPages(resolve)
            } catch (e) {
                reject(e)
            }
        })
    }
    // 导出pdf
    async dealPages(resolve) {
        // 绘制首页
        this.drawCopy({ canvas: this.options.homeCanvas }); // 绘制副本
        this.turnCanvasToPdf(); // 塞入pdf文件里边
        for (let i = 0; i < this.options.pagesCanvas.length; i++) { // 遍历获取每一部分
            this.options.copyCanvasRemainingH = 0
            this.options.currentCanvasRemainingH = 0
            this.options.standardCutHeight = 0 // 标准允许截断的高度，剩余高度如果在允许截断的高度之内，则去检测截断位置，否则直接从剩余高度底部位置截断
            await this.addPage(); // 先添加一页
            let els = this.options.pagesCanvas[i]
            for (let j = 0; j <= els.length; j++) { // 遍历每一个对象
                let canvas = null;
                if (this.options.currentCanvasRemainingH > 0) { // 如果上一页没画完，接着画上一页剩余的东西，其实这里可以用 currentCanvasRemainingH 来计算的，但是刚开始没想到，于是就这么扔这儿了，想优化的可以把这里修改一下
                    await this.addPage();
                    j -= 1; // pages 对应的还是上一页
                }
                canvas = els[j]; // 还有这里，可以直接用 pages[i] 来获取，因为在上一行做了 i -= 1 的操作，这里你们看着来就行，我就不做优化了，等封装的时候再做优化
                if (canvas) { // 这里是因为 上边的循环 用了 <= length ，所以最后一次会是 undefined,这里做个中断的操作
                    this.drawCopy({ canvas: canvas, headerFlag: true, footerFlag: true }); // 绘制副本
                    this.turnCanvasToPdf(); // 塞入pdf文件里边
                } else {
                    break;
                }
            }
        }
        if (this.options.showLoading) {
            this.hideLoading()
        }
        resolve(this)
    }
    // 上传
    uploadFile({ url, filename = '生成的pdf文件.pdf', params = {} }) {
        return new Promise((resolve, reject) => {
            if (this.options.showLoading) {
                this.showLoading('请勿关闭浏览器，文件上传中...')
            }
            let namesArr = filename.split('.')
            let ext = namesArr[namesArr.length - 1]
            if (ext !== 'pdf') {
                filename += '.pdf'
            }
            let blob = this.options.pdf.output('blob', { filename })
            let tmpFile = new File([blob], filename)
            this.upload(tmpFile, url, params, resolve, reject)
        })
    }
    // 导出
    exportFile(filename = '导出的pdf文件') {
        this.options.pdf.save(filename)
    }
    resetPreviewBox() {
        this.options.previewBox.innerHTML = ''
        this.resetFile()
    }
    resetFile() {
        let totalPage = this.options.totalPage
        while (totalPage > 0) {
            this.options.pdf.deletePage(totalPage)
            totalPage--
        }
        this.options.pdf.addPage()
        this.options.totalPage = 1;
        this.options.currentPage = 1;
        this.options.copyCanvasRemainingH = 0
        this.options.currentCanvasRemainingH = 0
    }
    // 绘制副本 
    drawCopy({ canvas, headerFlag = false, footerFlag = false }) {
        let diff = 0, // 计算差值
            calcHeight = 0, // 参与计算的高度
            useLessHeight = 98, // 剩余高度小于无用高度，直接舍弃 归 0
            // 准备 canvas 的切片需要的数据，具体参数详见：https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
            dw = {
                sx: 0, // 源x
                sy: 0, // 源y
                sw: 0, // 源w
                sh: 0, // 源h
                dx: 0, // 目标x
                dy: 0, // 目标y
                dw: 0, // 目标w
                dh: 0, // 目标h
            };
        if (this.options.currentCanvasRemainingH > 0) {
            // 如果有剩余 page 说明上一个 canvas 画了一部分，还剩余一部分没画上去，canvas还是之前的那个 canvas 对象
            this.createTempCanvas(); // 创建一个新的临时画布
            calcHeight = this.options.copyCanvas.height;
            if (headerFlag) {
                // 有页眉
                calcHeight -= this.options.headCanvas.height;
            }
            if (footerFlag) {
                // 有页脚
                calcHeight -= this.options.footCanvas.height;
            }
            diff = this.options.currentCanvasRemainingH - calcHeight;
            dw.sx = 0;
            dw.sy = canvas.height - this.options.currentCanvasRemainingH;
            dw.sw = canvas.width;
            dw.dx = 0;
            dw.dy = headerFlag ? this.options.headCanvas.height : 0;
            dw.dw = this.options.copyCanvas.width;
            if (diff > 0) {
                // 当前绘制对象剩余高度大于临时 canvas 对象高度
                dw.sh = calcHeight;
                dw.dh = calcHeight;
                // 因为需要截断当前页面，所以需要计算截断的点位
                let position = this.checkCutPosition(canvas, dw.sy + dw.sh, dw.sy + dw.sh - this.options.standardCutHeight);
                let positionDiff = dw.sy + dw.sh - position
                dw.sh = position - dw.sy
                dw.dh = position - dw.sy
                this.options.currentCanvasRemainingH = diff + positionDiff;
                this.options.copyCanvasRemainingH = 0;
            } else {
                // 当前绘制对象剩余高度小于或者等于临时 canvas 对象高度
                dw.sh = this.options.currentCanvasRemainingH;
                dw.dh = this.options.currentCanvasRemainingH;

                this.options.currentCanvasRemainingH = 0;
                let tmpCopyCanvasRemainingH = Math.abs(diff)
                this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < useLessHeight ? 0 : tmpCopyCanvasRemainingH;
            }
        } else {
            // 没有剩余 page 说明这里是一个新的 canvas
            if (this.options.copyCanvasRemainingH > 0) {
                // 如果临时 canvas 对象剩余高度 > 0,说明临时画布没画满，继续在当前画布上画一个新的 canvas 对象
                diff = canvas.height - this.options.copyCanvasRemainingH;
                dw.sx = 0;
                dw.sy = 0;
                dw.sw = canvas.width;
                dw.dx = 0;
                dw.dy =
                    this.options.copyCanvas.height -
                    this.options.copyCanvasRemainingH -
                    (headerFlag ? this.options.headCanvas.height : 0);
                dw.dw = this.options.copyCanvas.width;
                if (diff > 0) {
                    // 当前绘制对象高度比临时 canvas 剩余的高度大
                    dw.sh = this.options.copyCanvasRemainingH;
                    dw.dh = this.options.copyCanvasRemainingH;

                    // 因为需要截断当前页面，所以需要计算截断的点
                    let position = this.checkCutPosition(canvas, dw.sh, dw.sh > this.options.standardCutHeight ? (dw.sh - this.options.standardCutHeight) : 0);
                    let positionDiff = dw.sh - position
                    dw.sh = position
                    dw.dh = position

                    this.options.currentCanvasRemainingH = diff + positionDiff;
                    this.options.copyCanvasRemainingH = 0;
                } else {
                    // 当前绘制对象高度比临时 canvas 剩余的高度小或相等
                    dw.sh = canvas.height;
                    dw.dh = canvas.height;

                    this.options.currentCanvasRemainingH = 0;
                    let tmpCopyCanvasRemainingH = Math.abs(diff)
                    this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < useLessHeight ? 0 : tmpCopyCanvasRemainingH;
                }
            } else {
                // 临时 canvas 没有剩余高度，用一个新的 临时 canvas 对象画
                this.createTempCanvas(); // 创建临时画布
                calcHeight = this.options.copyCanvas.height;
                if (headerFlag) {
                    // 有页眉
                    calcHeight -= this.options.headCanvas.height;
                }
                if (footerFlag) {
                    // 有页脚
                    calcHeight -= this.options.footCanvas.height;
                }
                this.options.standardCutHeight = Math.floor(calcHeight * 0.3)
                diff = canvas.height - calcHeight;
                dw.sx = 0;
                dw.sy = 0;
                dw.sw = canvas.width;
                dw.dx = 0;
                dw.dy = headerFlag ? this.options.headCanvas.height : 0;
                dw.dw = this.options.copyCanvas.width;
                if (diff > 0) {
                    dw.sh = calcHeight;
                    dw.dh = calcHeight;
                    // 因为需要截断当前页面，所以需要计算截断的点位
                    let position = this.checkCutPosition(canvas, dw.sh, dw.sh - this.options.standardCutHeight);
                    let positionDiff = dw.sh - position
                    dw.sh = position
                    dw.dh = position

                    this.options.currentCanvasRemainingH = diff + positionDiff;
                    this.options.copyCanvasRemainingH = 0;
                } else {
                    // 当前绘制对象高度比临时 canvas高度小或者相同
                    dw.sh = canvas.height;
                    dw.dh = canvas.height;

                    this.options.currentCanvasRemainingH = 0;
                    let tmpCopyCanvasRemainingH = Math.abs(diff)
                    this.options.copyCanvasRemainingH = tmpCopyCanvasRemainingH < useLessHeight ? 0 : tmpCopyCanvasRemainingH;
                }
            }
        }
        this.options.copyContext.drawImage(
            canvas,
            dw.sx,
            dw.sy,
            dw.sw,
            dw.sh,
            dw.dx,
            dw.dy,
            dw.dw,
            dw.dh
        );
        if (headerFlag) {
            // 有页眉
            this.options.copyContext.drawImage(
                this.options.headCanvas,
                0,
                0,
                this.options.headCanvas.width,
                this.options.headCanvas.height,
                0,
                0,
                this.options.copyCanvas.width,
                this.options.headCanvas.height
            );
        }
        if (footerFlag) {
            // 有页脚
            this.options.copyContext.drawImage(
                this.options.footCanvas,
                0,
                0,
                this.options.footCanvas.width,
                this.options.footCanvas.height,
                0,
                this.options.copyCanvas.height - this.options.footCanvas.height,
                this.options.copyCanvas.width,
                this.options.footCanvas.height
            );
        }
    }
    // 找到当前点位是否可以截断，防止分页的时候文字分成了一半
    checkCutPosition(canvas, start, end) {
        let ctx = canvas.getContext("2d", { willReadFrequently: true })
        let checkRows = 0; // 记录白色行数
        let position = start // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
        let endPosition = end; // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
        // if(this.options.ratio != 1){
        //     position = start / this.options.ratio // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
        //     endPosition = end / this.options.ratio; // 这里需要除以像素比率，否则下方获取不到截断位置的颜色，将全部是白色，不知道什么情况，反正后边除以这个就正常了
        // }
        // 从定好的高度页面底部开始循环遍历canvas的每个点，找到可以截断的地方
        for (let i = position; i >= endPosition; i--) {
            
            let checkCols = 0; // 记录非白色点数
            let isWhite = true; // 是否是白色
            for (let j = 0; j < canvas.width; j++) {
                let canvasData = ctx.getImageData(j, i, 1, 1).data;
                // 如果该单位的颜色不是白色c[0]  c[1]  c[2] 分别代表r g b 255
                if (
                    canvasData[0] != 0xff ||
                    canvasData[1] != 0xff ||
                    canvasData[2] != 0xff
                ) {
                    checkCols++; // 非白色 + 1
                }
                // 如果这行有超过max_unit个单位都不是白色，退出当前循环
                let max_unit = 10
                if (checkCols > max_unit) {
                    isWhite = false;
                    checkCols = 0
                    break;
                }
            }
            if (isWhite) {
                checkRows++;
                // 如果有超过white_lines行都是白色的，canvas在这里可以截断了
                let white_lines = 8
                if (checkRows >= white_lines) {
                    position = i + white_lines / 2;
                    break;
                }
            } else {
                checkRows = 0;
            }
        }
        // if(this.options.ratio != 1){
        //     return position * this.options.ratio // 这里需要再乘以像素比率
        // } else {
            return position // 这里需要再乘以像素比率
        // }
    }
    // 转换pdf
    turnCanvasToPdf() {
        // 转换 pdf 一只往里边塞入创建的临时 canvas 就行，所有的处理都在临时 canvas 上处理过了
        let canvas = this.options.copyCanvas;
        let cvsWidth = canvas.width;
        let cvsHeight = canvas.height;

        this.options.pdf.addImage(
            canvas.toDataURL("image/jpeg", 1.0),
            "jpeg",
            0,
            0,
            this.options.a4[72][0],
            (this.options.a4[72][0] / cvsWidth) * cvsHeight
        );
        if (this.options.previewBox) {
            this.options.previewBox.appendChild(canvas)
        }
    }
    // 添加一页PDF
    async addPage() {
        this.options.pdf.addPage();
        this.options.totalPage += 1
        this.options.currentPage += 1; // 当前页 + 1
        await this.generateFooterCanvas()
    }
    // 生成页脚页码
    async generateFooterCanvas() {
        // 由于要绘制页码，这里需要重新获取一下页脚的 element 并修改页码，再次绘制出来页脚的 canvas
        let pagerEl = this.options.footEl.querySelector("#pageNumber");
        pagerEl.innerHTML = `${this.options.currentPage - 1}`; // 由于首页不需要首页页码，所以这里减去首页的页码 1
        this.options.footCanvas = await html2canvas(this.options.footEl, this.options.html2canvasConfig);
    }
    showLoading(title = 'loading...') {
        this.options.loading = this.$Vim.$loading({
            lock: true,
            text: title,
            spinner: 'el-icon-loading',
            background: 'rgba(0, 0, 0, 0.7)'
        })
    }
    hideLoading() {
        this.options.loading.close()
        this.options.loading = null
    }
    // 上传方法
    upload(file, url, others = {}, resolve = () => { }, reject = () => { }) {
        let self = this
        let data = Object.assign({ type: 'file' }, others)
        let formData = new FormData();
        for (let k in data) {
            formData.append(k, data[k]);
        }
        formData.append('file', file);
        let xhr = this.getXhr();
        xhr.open('POST', url, true);
        xhr.withCredentials = true;
        xhr.send(formData);
        // 处理返回数据
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (self.options.showLoading) {
                    self.hideLoading()
                }
                if (xhr.status == 200) {
                    resolve(JSON.parse(xhr.response))
                } else {
                    reject(JSON.parse(xhr.response))
                }
            }
        }
    }
    getXhr() {
        let xhr = null;
        if (window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        } else {
            //为了兼容IE6
            xhr = new ActiveXObject('Microsoft.XMLHTTP');
        }
        return xhr;
    }
}

export default Methods